# Чистые функции

Еще одна концепция, которую Scala предлагает для помощи в написании функционального кода, — 
это возможность писать чистые функции. 
Чистая функция (_pure function_) может быть определена следующим образом:

- функция `f` является чистой, если при одних и тех же входных данных `x` она всегда возвращает один и тот же результат `f(x)`
- результат функции зависит только от входных данных и её реализации
- чистые функции только вычисляют результат, ничего не меняя за пределами этих функций

Из этого следует:

- чистая функция не изменяет свои входные параметры
- она не мутирует какое-либо скрытое состояние
- у неё нет "черных ходов": он не читает данные из внешнего мира 
(включая консоль, веб-сервисы, базы данных, файлы и т. д.) и не записывает данные вовне

В результате этого определения каждый раз, когда вызывается чистая функция 
с одним и тем же входным значением (значениями), всегда будет выдаваться один и тот же результат. 
Например, можно вызывать функцию `double` бесконечное число раз с входным значением `2`, и всегда получать результат `4`.

### Примеры чистых функций

Учитывая это определение, методы в пакете `scala.math._` являются чистыми функциями:

- `abs`
- `ceil`
- `max`

Эти методы `String` также являются чистыми функциями:

- `isEmpty`
- `length`
- `substring`

Большинство методов в классах коллекций Scala также работают как чистые функции, 
включая `drop`, `filter`, `map` и многие другие.

> В Scala функции и методы почти полностью взаимозаменяемы, поэтому, 
> хотя здесь используется общепринятый отраслевой термин "чистая функция", 
> этот термин можно использовать как для описания функций, так и методов. 
> Как методы могут использоваться подобно функциям описано в главе [Eta расширение](../functions/eta.md).

### Примеры "грязных" функций

И наоборот, следующие функции "грязные" (_impure_), потому что они нарушают определение _pure function_:

- `println` — методы, взаимодействующие с консолью, файлами, базами данных, веб-сервисами и т.д., "грязные"
- `currentTimeMillis` — все методы, связанные с датой и временем, "грязные", 
потому что их вывод зависит от чего-то другого, кроме входных параметров
- `sys.error` — методы генерации исключений "грязные", потому что они не "просто возвращают результат"

"Грязные" функции часто делают одно из следующего:

- читают из скрытого состояния, т.е. обращаются к параметрам и данным, 
не переданным в функцию явным образом в качестве входных параметров
- запись в скрытое состояние
- изменяют заданные им параметры или изменяют скрытые переменные, например, поля в содержащем их классе
- выполняют какой-либо ввод-вывод с внешним миром

> В общем, следует остерегаться функций с возвращаемым типом `Unit`. 
> Поскольку эти функции ничего не возвращают, логически единственная причина, по которой они когда-либо вызываются, - 
> это достижение какого-то побочного эффекта. Как следствие, часто использование этих функций является "грязным".

### Но грязные функции все же необходимы…

Конечно, приложение не очень полезно, если оно не может читать или писать во внешний мир, 
поэтому рекомендуется следующее:

> Напишите ядро вашего приложения, используя только "чистые" функции, 
> а затем напишите "грязную" "оболочку" вокруг этого ядра для взаимодействия с внешним миром. 
> Как кто-то однажды сказал, это все равно, что положить слой нечистой глазури на чистый торт.

Важно отметить, что есть способы сделать "нечистое" взаимодействие с внешним миром более "чистым". 
Например, можно услышать об использовании `IO` монады для обработки ввода-вывода. 
Эти темы выходят за рамки данного документа, поэтому для простоты можно думать, 
что ФП приложения имеют ядро из "чистых" функций, 
которые объединены с другими функциями для взаимодействия с внешним миром.

### Написание "чистых" функций

**Примечание**: в этом разделе для обозначения методов Scala 
часто используется общепринятый в отрасли термин "чистая функция".

Для написания чистых функций на Scala, достаточно писать их, используя синтаксис методов Scala 
(хотя также можно использовать и синтаксис функций Scala). 
Например, вот чистая функция, которая удваивает заданное ей входное значение:

```dotty
def double(i: Int): Int = i * 2
```

Вот чистая функция, которая вычисляет сумму списка целых чисел с использованием рекурсии:

```dotty
def sum(xs: List[Int]): Int = xs match
  case Nil => 0
  case head :: tail => head + sum(tail)
```

Вышеописанные функции соответствуют определению "чистых".

### Ключевые моменты

Первым ключевым моментом этого раздела является определение чистой функции:

> Чистая функция — это функция, которая зависит только от своих объявленных входных данных 
> и своей реализации для получения результата. 
> Она только вычисляет свой результат, не завися от внешнего мира и не изменяя его.

Второй ключевой момент заключается в том, что каждое реальное приложение взаимодействует с внешним миром. 
Таким образом, упрощенный способ представления о функциональных программах состоит в том, 
что они состоят из ядра чистых функций, которые обернуты другими функциями, взаимодействующими с внешним миром.


---

**Ссылки:**

- [Pure Functions](https://danielasfregola.com/2015/04/07/pure-functions)
- [Scala3 book](https://docs.scala-lang.org/scala3/book/fp-pure-functions.html)
